1<script setup lang="ts">
2import A from "@/components/content/ProseA.vue";
3
4const config = useRuntimeConfig().public;
5const route = useRoute();
6
7const { data: post } = await useAsyncData(`post-${route.path}`, () =>
8 queryCollection("posts").path(route.path).first()
9);
10
11useSeoMeta({
12 title: post.value?.title,
13 description: post.value?.description
14});
15
16if (post.value) {
17 defineOgImageComponent("Post", {
18 title: post.value.title,
19 description: post.value.description,
20 date: post.value.date,
21 author: post.value.authors[0]?.name
22 });
23}
24</script>
25
26<template>
27 <div
28 class="grow"
29 >
30 <NuxtLink
31 class="print:hidden flex items-center gap-1 text-sm text-neutral-600 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-neutral-100 mt-4"
32 to="/"
33 >
34 <Icon name="ri:arrow-drop-left-line" mode="svg" />
35 Go back
36 </NuxtLink>
37 <article
38 v-if="post"
39 class="pb-24"
40 >
41 <header class="mb-8 mt-4">
42 <h1 class="text-4xl font-bold">
43 {{ post.title }}
44 </h1>
45 <div class="flex flex-wrap items-center justify-start gap-4 mt-4 text-neutral-600 dark:text-neutral-300">
46 <div v-if="post.authors" class="flex items-center gap-1">
47 <img v-if="post.authors[0]?.name === config.author" src="/logo.png" :alt="post.authors[0].name"
48 class="w-8 h-8 rounded-full mr-2">
49 <span v-for="author in post.authors" :key="author.name">
50 {{ author.name }}
51 </span>
52 </div>
53 ·
54 <span>
55 {{ new Date(post.date).toLocaleDateString('en-GB', {
56 year: 'numeric',
57 month: 'long',
58 day: 'numeric'
59 }) }}
60 </span>
61 <span v-if="post.updated">
62 <span class="text-neutral-500 dark:text-neutral-400 ml-2 mr-4">·</span>
63 <span class="text-sm">
64 Updated: {{ new Date(post.updated).toLocaleDateString('en-GB', {
65 year: 'numeric',
66 month: 'long',
67 day: 'numeric'
68 }) }}
69 </span>
70 </span>
71 ·
72 <div>
73 <span v-for="tag in post.tags" :key="tag" class="mr-2 mb-2 px-3 py-1 text-xs md:text-sm bg-stone-200 dark:bg-stone-700 text-stone-600 dark:text-stone-400 rounded-full">
74 {{ tag }}
75 </span>
76 </div>
77 </div>
78
79 <div class="bg-stone-200 dark:bg-stone-700 h-[1px] my-4"></div>
80
81 <p
82 class="text-md text-neutral-500 dark:text-neutral-400 leading-7 my-8 text-justify md:w-[80%] mx-auto"
83 >
84 {{ post.description }}
85 </p>
86 </header>
87
88 <div class="bg-stone-200 dark:bg-stone-700 h-[1px] my-8 md:w-[80%] mx-auto"></div>
89
90 <TableOfContents v-if="config.tableOfContents" :post="post" />
91
92 <ContentRenderer :value="post" class="post-body prose prose-lg leading-7 prose-slate dark:prose-invert text-justify md:w-[80%] mx-auto text-zinc-800 dark:text-zinc-200" />
93
94 <ShareActions :title="post.title" :description="post.description" :author="post.authors[0]?.name" />
95
96 <Suspense>
97 <BskyComments v-if="post.bskyCid" :cid="post.bskyCid" />
98
99 <template #fallback>
100 <h1 class="md:w-[80%] mx-auto mt-16 text-xl font-bold text-stone-600">Loading comments...</h1>
101 </template>
102 </Suspense>
103
104 </article>
105
106 <div v-else class="flex items-center justify-center">
107 <article class="grid place-items-center gap-6 mt-12">
108 <h2 class="text-3xl font-bold text-gray-900 dark:text-gray-200">It seems you might be lost...</h2>
109 <p class="text-gray-500 dark:text-gray-400">Please check the URL and try again.</p>
110
111 <div></div>
112
113 <div class="flex flex-row gap-2 items-baseline">
114 <A href="/" target="_self" class="text-lg">
115 Take me home!</A>
116 <span class="text-gray-500 dark:text-gray-400 text-sm">(country roads)</span>
117 </div>
118 </article>
119 </div>
120 </div>
121</template>
122
123<style>
124.post-body p:first-of-type::first-line {
125 font-weight: bold;
126}
127
128:target:before {
129 content: "";
130 display: block;
131 height: 2rem;
132 margin: -2rem 0 0;
133}
134
135</style>